﻿using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using GiveAQuiz.Data;
using GiveAQuiz.Web.Authentication;
using GiveAQuiz.Web.Helpers;
using GiveAQuiz.Web.Membership;
using GiveAQuiz.Web.Messenger;
using GiveAQuiz.Web.Models;
using GiveAQuizSite.Controllers;
using Moq;
using MvcContrib.TestHelper;
using NUnit.Framework;

namespace GiveAQuizSite.Tests.Controllers
{
  [TestFixture]
  public class RegistrationControllerTest : AuthenticationControllerTest
  {
    [Test]
    public void Constructor_should_set_Properties_correctly()
    {
      var mockRepository = new Mock<IQuizRepository>();
      var mockFormsAuth = new Mock<IFormsAuthentication>();
      var mockMembershipSvc = new Mock<IMembershipService>();
      var mockAcctMessenger = new Mock<IAccountMessenger>();

      var controller =
        new RegistrationController(mockRepository.Object, mockFormsAuth.Object, mockMembershipSvc.Object, mockAcctMessenger.Object);

      Assert.AreSame(mockRepository.Object, controller.Repository);
      Assert.AreSame(mockFormsAuth.Object, controller.FormsAuth);
      Assert.AreSame(mockMembershipSvc.Object, controller.MembershipService);
      Assert.AreSame(mockAcctMessenger.Object, controller.AccountMessenger);
    }

    [Test]
    public void Index_should_return_Index_view()
    {
      var controller = GetRegistrationController();

      ViewResult result = controller.Index() as ViewResult;

      result.AssertViewRendered();

      Assert.AreEqual(controller.MembershipService.MinPasswordLength, (int)result.ViewData["PasswordLength"]);
    }

    [Test]
    public void Register_Post_returns_Index_View_if_ModelState_IsValid_is_false()
    {
      var controller = GetRegistrationController();
      
      var model = new RegistrationViewModel
      {
        UserName = null,
        FirstName = "foo",
        LastName = "bar",
        Email = "email",
        Password = "goodPass",
        RepeatPassword = "goodPass"
      };

      // add an error
      controller.ModelState.AddModelError("UserName", "Dummy");

      ViewResult result = controller.Register(model) as ViewResult;

      Assert.IsFalse(result.ViewData.ModelState.IsValid);

      result.AssertViewRendered().ForView("Index");
    }

    [Test]
    public void Register_Post_calls_MembershipService_CreateUser()
    {
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        FirstName = "foo",
        LastName = "bar",
        Password = "password",
        RepeatPassword = "password",
        Email = mockUser.Object.Email
      };

      // set up the mock membership for CreateUser() call
      var mockMembershipService = CreateMockMembershipServiceForSuccess(mockUser.Object, model);         

      var controller = GetRegistrationController(mockMembershipService.Object);
      
      ViewResult result = controller.Register(model) as ViewResult;

      // verify 
      mockMembershipService.VerifyAll();
    }

    [Test]
    public void Register_Post_calls_Repository_CreateUser()
    {
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        Password = "password",
        RepeatPassword = "password",
        FirstName = "foo", 
        LastName = "bar",
        Email = mockUser.Object.Email
      };

      // set up the mock membership for CreateUser() call
      var mockMembershipService = CreateMockMembershipServiceForSuccess(mockUser.Object, model);

      var controller = GetRegistrationController(mockMembershipService.Object);

      ViewResult result = controller.Register(model) as ViewResult;

      MockRepository.Verify(r => r.SaveUser(It.Is<QuizUser>(qu =>
        qu.Id == (Guid)mockUser.Object.ProviderUserKey &&
        qu.FirstName.Equals(model.FirstName) &&
        qu.LastName.Equals(model.LastName))));
    }

    [Test]
    public void Register_Post_calls_MembershipService_UpdateUser_with_IsApproved_of_false_if_successful()
    {
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        FirstName = "foo",
        LastName = "bar",
        Password = "password",
        RepeatPassword = "password",
        Email = mockUser.Object.Email
      };

      // set up the mock membership for CreateUser() call
      var mockMembershipService = CreateMockMembershipServiceForSuccess(mockUser.Object, model);
      mockMembershipService
        .Setup(m => m.UpdateUser(mockUser.Object))
        .Callback<MembershipUser>(u => Assert.IsFalse(u.IsApproved))
        .Verifiable();        

      var controller = GetRegistrationController(mockMembershipService.Object);

      ViewResult result = controller.Register(model) as ViewResult;

      // verify 
      mockMembershipService.VerifyAll();
    }

    [Test]
    public void Register_Post_calls_AccountMessenger_SendRegistrationVerificationMessage_if_successful()
    {
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        FirstName = "foo",
        LastName = "bar",
        Password = "password",
        RepeatPassword = "password",
        Email = mockUser.Object.Email
      };

      // set up the mock membership 
      var mockMembershipService = CreateMockMembershipServiceForSuccess(mockUser.Object, model);

      // set up the mock account messenger for SendRegistrationVerificationMessage() call
      MockAccountMessenger
        .Setup(a => a.SendRegistrationVerificationMessage(mockUser.Object))
        .Verifiable();

      var controller = GetRegistrationController(mockMembershipService.Object);

      ViewResult result = controller.Register(model) as ViewResult;

      // verify 
      MockAccountMessenger.VerifyAll();
    }

    [Test]
    public void Register_Post_returns_View_with_ModelState_Error_if_MembershipService_CreateUser_is_unsuccessful()
    {
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        FirstName = "foo",
        LastName = "bar",
        Password = "password",
        RepeatPassword = "password",
        Email = mockUser.Object.Email
      };

      // set up the mock membership 
      var mockMembershipService = new Mock<IMembershipService>();
      mockMembershipService
        .Setup(m => m.CreateUser(model.UserName, model.Password, model.Email))
        .Returns(MembershipCreateStatus.InvalidUserName)
        .Verifiable();          

      var controller = GetRegistrationController(mockMembershipService.Object);

      ViewResult result = controller.Register(model) as ViewResult;

      // verify 
      Assert.IsFalse(result.ViewData.ModelState.IsValid);
      Assert.AreEqual(
        MembershipCreateStatus.InvalidUserName.PrintErrorMessage(), 
        result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage);
      result.AssertViewRendered().ForView("Index");      
    }

    [Test]
    public void Register_Post_returns_Verify_View_if_successful()
    {
      // Arrange
      var mockUser = CreateMockUser();
      var model = new RegistrationViewModel
      {
        UserName = mockUser.Object.UserName,
        FirstName = "foo",
        LastName = "bar",
        Password = "password",
        RepeatPassword = "password",
        Email = mockUser.Object.Email
      };

      var mockMembershipService = CreateMockMembershipServiceForSuccess(mockUser.Object, model);         
      
      RegistrationController controller = GetRegistrationController(mockMembershipService.Object);

      // Act
      ViewResult result = controller.Register(model) as ViewResult;

      // Assert
      result.AssertViewRendered().ForView("Verify");
    }

    private Mock<IMembershipService> CreateMockMembershipServiceForSuccess(
      MembershipUser user, RegistrationViewModel model)
    {
      // set up the mock membership for CreateUser() and GetUser() calls
      var mockMembershipService = new Mock<IMembershipService>();
      mockMembershipService
        .Setup(m => m.CreateUser(model.UserName, model.Password, model.Email))
        .Returns(MembershipCreateStatus.Success)
        .Verifiable();
      mockMembershipService
        .Setup(m => m.GetUser(user.UserName))
        .Returns(user)
        .Verifiable();

      return mockMembershipService;
    }

    private RegistrationController GetRegistrationController()
    {
      MembershipProvider membershipProvider = new MockMembershipProvider();
      AccountMembershipService membershipService = new AccountMembershipService(membershipProvider);
      return GetRegistrationController(membershipService);
    }

    private RegistrationController GetRegistrationController(IMembershipService membershipService)
    {
      RegistrationController controller =
        new RegistrationController(MockRepository.Object, MockFormsAuthentication.Object, membershipService, MockAccountMessenger.Object);
      ControllerContext controllerContext = new ControllerContext(MockHttpContext.Object, new RouteData(), controller);
      controller.ControllerContext = controllerContext;
      return controller;
    }
  }
}
